#include "main_window.h"

#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QGridLayout>
#include <QLabel>
#include <QMenuBar>
#include <QProcess>
#include <QPushButton>
#include <QSignalMapper>
#include <QStatusBar>
#include <QTextStream>

#include <gecode/search.hh>

#include "core/music_settings.h"
#include "core/music_constraints.h"
#include "gui/about_dialog.h"
#include "gui/constraints_widget.h"
#include "gui/first_note_dialog.h"
#include "gui/midi_player_widget.h"
#include "gui/misc_dialog.h"
#include "gui/pattern_dialog.h"
#include "gui/scale_mode_dialog.h"
#include "gui/score_dialog.h"
#include "gui/score_widget.h"
#include "gui/tempo_dialog.h"

MainWindow::MainWindow(QWidget *parent):
  QMainWindow(parent) {
  setWindowTitle("Projet « Composition musicale »");
  resize(800, 600);

  settings = new MusicSettings;

  constraintsWidget = new ConstraintsWidget(settings, this);
  midiPlayerWidget = new MidiPlayerWidget(this);

  connect(constraintsWidget, SIGNAL(generate()), this, SLOT(generate()));

  connect(midiPlayerWidget, SIGNAL(playing()), this, SLOT(notReady()));
  connect(midiPlayerWidget, SIGNAL(playing()),
          constraintsWidget, SLOT(notReady()));

  connect(midiPlayerWidget, SIGNAL(stopped()), this, SLOT(ready()));
  connect(midiPlayerWidget, SIGNAL(stopped()),
          constraintsWidget, SLOT(ready()));

  scoreWidget = new ScoreWidget(this);
  connect(scoreWidget, SIGNAL(firstPageDisplayed()), this, SLOT(ready()));
  connect(scoreWidget, SIGNAL(firstPageDisplayed()),
          constraintsWidget, SLOT(ready()));
  connect(scoreWidget, SIGNAL(firstPageDisplayed()),
          midiPlayerWidget, SLOT(ready()));

  statusBarLabel = new QLabel(statusBar());
  statusBar()->addWidget(statusBarLabel);

  QBoxLayout *rightLayout = new QVBoxLayout;
  rightLayout->setContentsMargins(0, 0, 0, 0);
  rightLayout->addWidget(constraintsWidget);
  rightLayout->addWidget(midiPlayerWidget);
  rightLayout->addStretch(0);

  QHBoxLayout *mainLayout = new QHBoxLayout;
  mainLayout->setContentsMargins(0, 0, 0, 0);
  mainLayout->addWidget(scoreWidget);
  mainLayout->addLayout(rightLayout);

  QWidget *centralWidget = new QWidget(this);
  centralWidget->setLayout(mainLayout);

  createActions();
  createMenus();

  newMelodyInit();

  setCentralWidget(centralWidget);
}

MainWindow::~MainWindow() {
  delete settings;
}

/*!
 * Créé les actions Qt nécessaires pour les menus de l'interface
 */
void MainWindow::createActions() {
  QSignalMapper *signalMapper = new QSignalMapper(this);
  connect(signalMapper, SIGNAL(mapped(const QString &)),
          this, SLOT(changeStatusBar(const QString &)));

  // actions du menu Fichier

  newMelodyAction = new QAction("&Nouveau", this);
  connect(newMelodyAction, SIGNAL(triggered()), this, SLOT(newMelodyInit()));
  connect(newMelodyAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(newMelodyAction,
                           QString("Réinitialise les paramètres des "
                                   "contraintes pour créer une "
                                   "nouvelle mélodie"));

  //
  
  saveSettingsAction = new QAction("&Enregistrer les paramètres...", this);
  connect(saveSettingsAction, SIGNAL(triggered()), this, SLOT(saveSettings()));
  connect(saveSettingsAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(saveSettingsAction,
                           QString("Enregistre les paramètres de la mélodie"));

  loadSettingsAction = new QAction("&Charger des paramètres...", this);
  connect(loadSettingsAction, SIGNAL(triggered()), this, SLOT(loadSettings()));
  connect(loadSettingsAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(loadSettingsAction,
                           QString("Charge des paramètres de mélodie"));
  //

  exportLyAction = new QAction("Exporter le fichier &Lilypond...", this);
  connect(exportLyAction, SIGNAL(triggered()), this, SLOT(exportLy()));
  connect(exportLyAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(exportLyAction,
                           QString("Exporte la mélodie dans un "
                                   "fichier au format <b>Lilypond</b>"));

  exportMIDIAction = new QAction("Exporter le fichier &MIDI...", this);
  connect(exportMIDIAction, SIGNAL(triggered()), this, SLOT(exportMIDI()));
  connect(exportMIDIAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(exportMIDIAction,
                           QString("Exporte la mélodie dans un "
                                   "fichier au format <b>MIDI</b>"));

  exportPDFAction = new QAction("Exporter le fichier &PDF...", this);
  connect(exportPDFAction, SIGNAL(triggered()), this, SLOT(exportPDF()));
  connect(exportPDFAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(exportPDFAction,
                           QString("Exporte la mélodie dans un "
                                   "fichier au format <b>PDF</b>"));

  //

  quitAction = new QAction("&Quitter", this);
  connect(quitAction, SIGNAL(triggered()), this, SLOT(close()));
  connect(quitAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(quitAction,
                           QString("Quitte le programme"));

  // actions du menu Paramètres

  scoreSettingAction = new QAction("&Partition...", this);
  connect(scoreSettingAction, SIGNAL(triggered()), this, SLOT(scoreSetting()));
  connect(scoreSettingAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(scoreSettingAction,
                           QString("Configure le nombre de notes et "
                                   "le nombre d'octave"));

  tempoSettingAction = new QAction("T&empo...", this);
  connect(tempoSettingAction, SIGNAL(triggered()), this, SLOT(tempoSetting()));
  connect(tempoSettingAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(tempoSettingAction,
                           QString("Configure le tempo de la mélodie "
                                   "(pour le fichier MIDI)"));

  //

  scaleModeSettingAction = new QAction("&Tonalité et mode...", this);
  connect(scaleModeSettingAction, SIGNAL(triggered()),
          this, SLOT(scaleModeSetting()));
  connect(scaleModeSettingAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(scaleModeSettingAction,
                           QString("Configure la contrainte "
                                   "sur la tonalité et le mode"));

  ascendingScaleSettingAction = new QAction("&Ascendante", this);
  connect(ascendingScaleSettingAction, SIGNAL(triggered()),
          this, SLOT(ascendingScaleSetting()));
  connect(ascendingScaleSettingAction, SIGNAL(hovered()),
          signalMapper, SLOT(map()));
  signalMapper->setMapping(ascendingScaleSettingAction,
                           QString("Configure la contrainte "
                                   "de gamme ascendante"));

  descendingScaleSettingAction = new QAction("&Descendante", this);
  connect(descendingScaleSettingAction, SIGNAL(triggered()), 
          this, SLOT(descendingScaleSetting()));
  connect(descendingScaleSettingAction, SIGNAL(hovered()), 
          signalMapper, SLOT(map()));
  signalMapper->setMapping(descendingScaleSettingAction,
                           QString("Configure la contrainte "
                                   "de gamme descendante"));

  firstNoteSettingAction = new QAction("&Degré de la première note...", this);
  connect(firstNoteSettingAction, SIGNAL(triggered()),
          this, SLOT(firstNoteSetting()));
  connect(firstNoteSettingAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(firstNoteSettingAction,
                           QString("Configure la contrainte sur "
                                   "le degré de la première note"));

  patternSettingAction = new QAction("&Motif...", this);
  connect(patternSettingAction, SIGNAL(triggered()),
          this, SLOT(patternSetting()));
  connect(patternSettingAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(patternSettingAction,
                           QString("Configure une contrainte de motif"));

  miscSettingAction = new QAction("&Divers...", this);
  connect(miscSettingAction, SIGNAL(triggered()), this, SLOT(miscSetting()));
  connect(miscSettingAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(miscSettingAction,
                           QString("Configure divers paramètres"));

  // actions du menu Aide

  aboutAction = new QAction("À &propos...", this);
  connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
  connect(aboutAction, SIGNAL(hovered()), signalMapper, SLOT(map()));
  signalMapper->setMapping(aboutAction,
                           QString("Affiche des informations "
                                   "à propos du programme"));

}

/*!
 * Créé les menus de l'interface
 */
void MainWindow::createMenus() {
  // menu Fichier
  fileMenu = menuBar()->addMenu("&Fichier");
  fileMenu->addAction(newMelodyAction);
  fileMenu->addSeparator();
  fileMenu->addAction(saveSettingsAction);
  fileMenu->addAction(loadSettingsAction);
  fileMenu->addSeparator();
  fileMenu->addAction(exportLyAction);
  fileMenu->addAction(exportMIDIAction);
  fileMenu->addAction(exportPDFAction);
  fileMenu->addSeparator();
  fileMenu->addAction(quitAction);

  // menu Paramètres
  settingsMenu = menuBar()->addMenu("&Paramètres");
  settingsMenu->addAction(scoreSettingAction);
  settingsMenu->addAction(tempoSettingAction);
  settingsMenu->addSeparator();
  settingsMenu->addAction(scaleModeSettingAction);
  scaleMenu = settingsMenu->addMenu("Gammes");
  scaleMenu->addAction(ascendingScaleSettingAction);
  scaleMenu->addAction(descendingScaleSettingAction);
  settingsMenu->addSeparator();
  settingsMenu->addAction(firstNoteSettingAction);
  settingsMenu->addAction(patternSettingAction);
  settingsMenu->addAction(miscSettingAction);

  // menu Aide
  helpMenu = menuBar()->addMenu("&Aide");
  helpMenu->addAction(aboutAction);
}

/*!
 * Affiche des informations sur l'application
 */
void MainWindow::about() {
  AboutDialog aboutDialog(this);
  aboutDialog.exec();
}

/*!
 * Génénère une nouvelle mélodie à partir du CSP
 */
void MainWindow::generate() {
  newMelody();

  MusicConstraints mc(settings);
  DFS<MusicConstraints> engine(&mc);

  MusicConstraints *next;
  if((next = engine.next())) {
    constraintsWidget->notReady();

    // on exporte les 3 fichiers .ly maintenant pour éviter
    // de garder l'espace Gecode en mémoire

    // pour l'affichage dans scoreWidget
    next->writeLy("tmp-internal.ly", LilyPondWriter::INTERNAL);
    // pour l'export au format Lilypond
    next->writeLy("tmp-lilypond.ly", LilyPondWriter::LILYPOND);
    // pour l'export au format PDF
    next->writeLy("tmp-pdf.ly", LilyPondWriter::PDF);
    statusBarLabel->setText("Création des images de la partition en cours...");
    scoreWidget->displayScore();
    delete next;
  }
  else {
    newMelody();
    statusBarLabel->setText("Impossible de générer une mélodie, "
                            "le CSP est <b>surcontraint</b>");
    constraintsWidget->ready();
  }
}

/*!
 * Nettoie les fichiers temporaires créés lors de la génération de la mélodie
 */
void MainWindow::cleanFiles() {
  QStringList files = QDir::current().entryList(QStringList() << "tmp*.ly"
                                                              << "tmp*.midi"
                                                              << "tmp*.png"
                                                              << "tmp*.ps");
  foreach(QString filename, files)
    QFile::remove(filename);
}

/*!
 * Événement Qt de fermeture de fenêtre
 * Nettoie les fichiers temporaires et arrête la lecture du fichier MIDI
 */
void MainWindow::closeEvent(QCloseEvent *) {
  cleanFiles();
  midiPlayerWidget->stop();
}

/*!
 * Désactive les actions des menus
 */
void MainWindow::notReady() {
  newMelodyAction->setDisabled(true);
  exportLyAction->setDisabled(true);
  exportMIDIAction->setDisabled(true);
  exportPDFAction->setDisabled(true);
}

/*!
 * Active les actions des menus
 */
void MainWindow::ready() {
  statusBarLabel->setText("Cliquez sur le bouton <b>Écouter</b> "
                          "pour écouter la mélodie générée");

  newMelodyAction->setEnabled(true);
  exportLyAction->setEnabled(true);
  exportMIDIAction->setEnabled(true);
  exportPDFAction->setEnabled(true);
}

/*!
 * Change le message de la barre de statut
 */
void MainWindow::changeStatusBar(const QString &message) {
  statusBarLabel->setText(message);
}

/*!
 * Prépare l'interface pour générer une nouvelle mélodie et
 * réinitialise les paramètres du CSP
 */
void MainWindow::newMelodyInit() {
  settings->initialize();
  constraintsWidget->updateInfos();

  scaleMenu->setDisabled(true);
  firstNoteSettingAction->setDisabled(true);

  newMelody();
}

/*!
 * Prépare l'interface pour générer une nouvelle mélodie
 */
void MainWindow::newMelody() {
  cleanFiles();
  notReady();

  statusBarLabel->setText("Cliquez sur le bouton <b>Générer</b> "
                          "pour composer une mélodie");

  scoreWidget->initialize();

  midiPlayerWidget->notReady();
}

/*!
 * Enregistre les paramètres dans un fichier
 */
void MainWindow::saveSettings() {
  QString filename = QFileDialog::getSaveFileName(this,
                                                  "Enregistrer les paramètres",
                                                  QDir::homePath(),
                                                  "*.conf");

  if(!filename.isEmpty()) {
    filename.remove(QRegExp(".conf$"));
    QFile file(filename + ".conf");
    if (!file.open(QIODevice::WriteOnly))
      statusBarLabel->setText("<b>Les paramètres n'ont pas pu "
                              "être enregistrés !</b>");

    QTextStream out(&file);
    settings->write(out);

    statusBarLabel->setText("<b>Paramètres enregistrés avec succès !</b>");
  }
}

/*!
 * Charge les paramètres à partir d'un fichier
 */
void MainWindow::loadSettings() {
  QString filename = QFileDialog::getOpenFileName(this,
                                                  "Charger des paramètres",
                                                  QDir::homePath(),
                                                  "*.conf");

  if(!filename.isEmpty()) {
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly))
      statusBarLabel->setText("<b>Les paramètres n'ont pas pu "
                              "être chargés !</b>");

    QTextStream in(&file);
    settings->read(in);

    statusBarLabel->setText("<b>Paramètres chargés avec succès !</b>");
    constraintsWidget->updateInfos();
  }
}

/*!
 * Exporte la mélodie au format Lilypond.
 */
void MainWindow::exportLy() {
  QString filename = QFileDialog::getSaveFileName(this,
                                                  "Exporter au format Lilypond",
                                                  QDir::homePath(),
                                                  "*.ly");

  if(!filename.isEmpty()) {
    filename.remove(QRegExp(".ly$"));
    QFile::copy("tmp-lilypond.ly", filename + ".ly");
    statusBarLabel->setText("<b>Fichier Lilypond exporté avec succès !</b>");
  }
}

/*!
 * Exporte la mélodie au format MIDI.
 */
void MainWindow::exportMIDI() {
  QString filename = QFileDialog::getSaveFileName(this,
                                                  "Exporter au format MIDI",
                                                  QDir::homePath(),
                                                  "*.midi");

  if(!filename.isEmpty()) {
    filename.remove(QRegExp(".midi$"));
    QFile::copy("tmp.midi", filename + ".midi");
    statusBarLabel->setText("<b>Fichier MIDI exporté avec succès !</b>");
  }
}

/*!
 * Exporte la mélodie au format PDF
 */
void MainWindow::exportPDF() {
  QString filename = QFileDialog::getSaveFileName(this,
                                                  "Exporter au format PDF",
                                                  QDir::homePath(),
                                                  "*.pdf");

  if(!filename.isEmpty()) {
    filename.remove(QRegExp(".pdf$"));
    QProcess::execute("lilypond --pdf --output=\"" +
                      filename +
                      "\" tmp-pdf.ly");
    QFile::remove(filename + ".ps");
    statusBarLabel->setText("<b>Fichier PDF exporté avec succès !</b>");
  }
}

/*!
 * Affiche une boîte de dialogue pour configurer le nombre de note de la
 * partition et le nombre d'octaves
 */
void MainWindow::scoreSetting() {
  ScoreDialog scoreDialog(settings, this);
  scoreDialog.exec();

  constraintsWidget->updateInfos();
}

/*!
 * Affiche une boîte de dialogue pour configurer le tempo
 */
void MainWindow::tempoSetting() {
  TempoDialog tempoDialog(settings, this);
  tempoDialog.exec();

  constraintsWidget->updateInfos();
}

/*!
 * Affiche une boîte de dialogue pour configurer la contrainte de la tonalité et
 * du mode
 */
void MainWindow::scaleModeSetting() {
  ScaleModeDialog scaleModeDialog(settings, this);
  scaleModeDialog.exec();

  firstNoteSettingAction->setDisabled(settings->scale ==
                                      MusicSettings::UNDEFINED_SCALE);
  scaleMenu->setDisabled(settings->scale == MusicSettings::UNDEFINED_SCALE);

  constraintsWidget->updateInfos();
}

/*!
 * Configure une gamme ascendante
 */
void MainWindow::ascendingScaleSetting() {
  settings->scoreSize = 8;
  settings->nbOctave = 2;
  settings->firstNote = MusicSettings::TONIC;
  settings->pattern = "U";
  settings->conjoint = true;

  constraintsWidget->updateInfos();
}

/*!
 * Configure une gamme descendante
 */
void MainWindow::descendingScaleSetting() {
  settings->scoreSize = 8;
  settings->nbOctave = 2;
  settings->firstNote = MusicSettings::TONIC;
  settings->pattern = "D";
  settings->conjoint = true;

  constraintsWidget->updateInfos();
}

/*!
 * Affiche une boîte de dialogue pour configurer le degré de la première note
 */
void MainWindow::firstNoteSetting() {
  FirstNoteDialog firstNoteDialog(settings, this);
  firstNoteDialog.exec();

  constraintsWidget->updateInfos();
}

/*!
 * Affiche une boîte de dialogue pour configurer un motif de mélodie
 */
void MainWindow::patternSetting() {
  PatternDialog patternDialog(settings, this);
  patternDialog.exec();

  constraintsWidget->updateInfos();
}

/*!
 * Affiche une boîte de dialogue pour configurer divers paramètres
 */
void MainWindow::miscSetting() {
  MiscDialog miscDialog(settings, this);
  miscDialog.exec();

  constraintsWidget->updateInfos();
}

